using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace CustomCMS.API.Util
{
    public class SolveIf
    {
        private static Regex FullIfRegex, SingleIfRegex, FalseRegex, TrueRegex, NegateRegex, WordRegex, SimpleRegex, AndRegex, OrRegex, ParenthesisRegex, QuotedRegex;

        static SolveIf()
        {
            FullIfRegex = new Regex("<cms:if(?:\\s+test\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'))?\\s*(?:>((?:.(?!</?cms:if))*.??)</cms:if\\s*>|/>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            SingleIfRegex = new Regex("<cms:(then|is_?true)\\s*>(.*?)</cms:\\1>|<cms:(else|is_?false)\\s*>(.*?)</cms:\\3>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            FalseRegex = new Regex("^\\s*(?:false|0|null|not?|none)\\s*$", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            TrueRegex = new Regex("^\\s*(?:true|yes)\\s*$", RegexOptions.Singleline | RegexOptions.IgnoreCase);
			NegateRegex = new Regex("(?<=^|!|\\(|&&|\\|\\|)\\s*!\\s*(-?[a-z0-9_.]+?)\\s*(?=$|\\)|&&|\\|\\|)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
			WordRegex = new Regex("(?<=^|!|\\(|&&|\\|\\|)\\s*([a-z_][a-z0-9_]*?)\\s*(?=$|\\)|&&|\\|\\|)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
			SimpleRegex = new Regex("(?<=^|!|\\(|&&|\\|\\|)\\s*(-?[a-z0-9_.]+?|'[^']*'|\"[^\"]*\")\\s*(!=|==?|>=?|<=?|=<|=>)\\s*(-?[a-z0-9_.]+?|'[^']*'|\"[^\"]*\")\\s*(?=$|\\)|&&|\\|\\|)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            AndRegex = new Regex("(?<=^|!|\\(|&&|\\|\\|)\\s*(-?[a-z0-9_.]+?)\\s*(&&)\\s*(-?[a-z0-9_.]+?)\\s*(?=$|\\)|&&|\\|\\|)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            OrRegex = new Regex("(?<=^|!|\\(|&&|\\|\\|)\\s*(-?[a-z0-9_.]+?)\\s*(\\|\\|)\\s*(-?[a-z0-9_.]+?)\\s*(?=$|\\)|&&|\\|\\|)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            ParenthesisRegex = new Regex("(?<=^|!|\\(|&&|\\|\\|)\\s*\\(\\s*(-?[a-z0-9_.]+?)\\s*\\)\\s*(?=$|\\)|&&|\\|\\|)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            QuotedRegex = new Regex("^'[^']*'$|^\"[^\"]*\"$", RegexOptions.Singleline);
        }

        private Dictionary<string, string[]> vars;
        private Dictionary<string, string> values;

        public SolveIf(Dictionary<string, string[]> templateVars, Dictionary<string, string> varValues)
        {
            vars = templateVars;
            values = varValues;
        }

        public void Solve(StringBuilder text)
        {
            string result = RecurseReplace(text.ToString(), FullIfRegex, SolveSingleIf);
            text.Remove(0, text.Length).Append(result);
        }

        private string RecurseReplace(string text, Regex regex, MatchEvaluator func)
        {
            string last, result = text;
            do result = regex.Replace(last = result, func); while (result != last);
            return result;
        }

        private string RecurseReplace(string text, Regex regex, string replacement)
        {
            string last, result = text;
            do result = regex.Replace(last = result, replacement); while (result != last);
            return result;
        }

        private string SolveSingleIf(Match match)
        {
            if (!match.Groups[1].Success && !match.Groups[2].Success) return string.Empty;
            string test = SolveTest(match.Groups[match.Groups[1].Success? 1 : 2].Value);
            return IsBool(test) ? SingleIfRegex.Replace(match.Groups[3].Value, ParseBool(test) ? "$2" : "$4") : test;
        }

        public string SolveTest(string test)
        {
            if (test == null || Regex.IsMatch(test, "^\\s*$")) return StrBool(false);

            string last, result = "!!(" + test + ")";
            do
            {
				result = RecurseReplace(last = result, WordRegex, SolveWord);
				result = RecurseReplace(last = result, NegateRegex, SolveNegation);
				result = RecurseReplace(result, SimpleRegex, SolveSimpleCondition);
                result = RecurseReplace(result, AndRegex, SolveSimpleCondition);
                result = RecurseReplace(result, OrRegex, SolveSimpleCondition);
                result = RecurseReplace(result, ParenthesisRegex, "$1");
            } while (result != last);
            return IsBool(result)? result : ("<!--\n/* Malformed test condition in IF element: " + test + " *///-->");
        }

		private string SolveWord(Match match) {
			string val;
			return StrBool(values.TryGetValue(match.Groups[1].Value, out val) ? IsBool(val) ? ParseBool(val) : val != null && val != "" : IsBool(match.Groups[1].Value) ? ParseBool(match.Groups[1].Value) : false);
		}

		private string SolveNegation(Match match) {
			string val;
			return values.TryGetValue(match.Groups[1].Value, out val) ? StrBool(IsBool(val) ? !ParseBool(val) : val == null || val == "") : IsBool(match.Groups[1].Value) ? StrBool(!ParseBool(match.Groups[1].Value)) : match.Value;
		}

		private string SolveSimpleCondition(Match match)
        {
            string op1, op2;
            float f1, f2, b1, b2;
            bool isNum1 = GetVar(match.Groups[1].Value, out op1, out f1, out b1), isNum2 = GetVar(match.Groups[3].Value, out op2, out f2, out b2), numComp = isNum1 && isNum2;
            bool isBool1 = !float.IsNaN(b1), isBool2 = !float.IsNaN(b2), boolComp = isBool1 && isBool2;
/*
            string op1 = GetVar(match.Groups[1].Value);
            string op2 = GetVar(match.Groups[3].Value);
            float f1 = ParseFloat(op1), f2 = ParseFloat(op2);
            if (float.IsNaN(f1)) f2 = IsBool(op1)? ParseBool(op1) ? 1 : 0 : ;
            if (float.IsNaN(f2) && IsBool(op2)) f2 = ParseBool(op2) ? 1 : 0;
            bool numComp = !float.IsNaN(f1) && !float.IsNaN(f2);
 */
            switch (match.Groups[2].Value)
            {
                case "==":
                case "=": return StrBool(numComp? f1 == f2 : op1 == op2);
                case "!=": return StrBool(numComp? f1 != f2 : op1 != op2);
                case ">=":
                case "=>": return StrBool(numComp? f1 >= f2 : op1.CompareTo(op2) >= 0);
                case "<=":
                case "=<": return StrBool(numComp? f1 <= f2 : op1.CompareTo(op2) <= 0);
                case ">": return StrBool(numComp ? f1 > f2 : op1.CompareTo(op2) > 0);
                case "<": return StrBool(numComp ? f1 < f2 : op1.CompareTo(op2) < 0);
                case "&&": return boolComp? StrBool(b1 != 0 && b2 != 0) : ((isBool1? StrBool(b1 != 0) : op1) + " && " + (isBool2? StrBool(b2 != 0) : op2));//StrBool(ParseBool(op1) && ParseBool(op2));
                case "||": return boolComp ? StrBool(b1 != 0 || b2 != 0) : ((isBool2 ? StrBool(b1 != 0) : op1) + " || " + (isBool2 ? StrBool(b2 != 0) : op2));//StrBool(ParseBool(op1) || ParseBool(op2));
            }
            return match.Value;
        }

        private string StrBool(bool b)
        {
            return b? "true" : "false";
        }

        private bool GetVar(string name, out string text, out float number, out float boolean)
        {
            bool recognized = true;
            if (QuotedRegex.IsMatch(name)) text = Regex.Replace(name, "^.|.$", "", RegexOptions.Singleline);
            else if (!values.TryGetValue(name, out text)) { recognized = false; text = name; }


            boolean = text == "" || text == null ? 0 : IsBool(text) ? ParseBool(text) ? 1 : 0 : recognized ? 1 : ParseFloat(text);
            number = ParseFloat(text);
            if (float.IsNaN(number) && IsBool(text)) number = ParseBool(text) ? 1 : 0;
            return !float.IsNaN(number);
 //                return Regex.IsMatch(name, "^'[^']*'$|^\"[^\"]*\"$") ? Regex.Replace(name, "^.|.$", "", RegexOptions.Singleline) : values.TryGetValue(name, out val) ? val : name;
        }

        private bool IsBool(string s)
        {
            try { ParseBool(s); return true; }
            catch (Exception) { return false; }
        }

        private bool ParseBool(string s)
        {
            return FalseRegex.IsMatch(s) ? false : TrueRegex.IsMatch(s) ? true : float.IsNaN(float.Parse(s)) ? false : float.Parse(s) != 0;
        }

        private float ParseFloat(string s)
        {
            float f;
            return float.TryParse(s, out f) ? f : float.NaN;
        }
    }
}
